home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Libris Britannia 4
/
science library(b).zip
/
science library(b)
/
TECHNICA
/
COMPUTER
/
H254.ZIP
/
IRITSM3S.ZIP
/
IRIT
/
OBJECTS.C
< prev
next >
Wrap
C/C++ Source or Header
|
1992-02-23
|
23KB
|
695 lines
/*****************************************************************************
* "Irit" - the 3d polygonal solid modeller. *
* *
* Written by: Gershon Elber Ver 0.2, Mar. 1990 *
******************************************************************************
* Module to handle the objects list - fetch, insert, delete etc... *
*****************************************************************************/
/* #define DEBUG Output goes to stdout if defined. */
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "program.h"
#include "allocate.h"
#include "dataprsr.h"
#include "attribut.h"
#include "geomat3d.h"
#include "graphgen.h"
#include "objects.h"
#include "windows.h"
static int GetDumpLevel(void);
static PolygonStruct *GenAxesObjectPolylines(void);
static void PrintOneLine(char *Line);
/*****************************************************************************
* Routine to set up all the predefined objects - objects that the system *
* must have all the time, like global transformation matrix. *
*****************************************************************************/
void SetUpPredefObjects(void)
{
RealType R;
MatrixType Mat1, Mat2;
ObjectStruct *PObj;
/* 90 - 35.2644 = 54.7356 */
MatGenMatRotX1(DEG2RAD(-54.7356), Mat1); /* Generate default view trans. */
MatGenMatRotZ1(M_PI+M_PI/4, Mat2); /* which is isometric view. */
MatMultTwo4by4(Mat2, Mat2, Mat1);
PObj = GenMatObject("VIEW_MAT", Mat2, NULL);
InsertObject(PObj);
MatGenUnitMat(Mat1); /* Generate default perspective trans. */
Mat1[2][2] = 0.1;
Mat1[2][3] = -0.35;
Mat1[3][2] = 0.35;
PObj = GenMatObject("PRSP_MAT", Mat1, NULL);
InsertObject(PObj);
R = DEFAULT_RESOLUTION;
PObj = GenNumObject("RESOLUTION", &R, NULL);
InsertObject(PObj);
R = DEFAULT_DRAW_CTLPT;
PObj = GenNumObject("DRAWCTLPT", &R, NULL);
InsertObject(PObj);
R = DEFAULT_INTERNAL;
PObj = GenNumObject("INTERNAL", &R, NULL);
InsertObject(PObj);
R = DEFAULT_INTERCRV;
PObj = GenNumObject("INTERCRV", &R, NULL);
InsertObject(PObj);
R = DEFAULT_ECHOSRC;
PObj = GenNumObject("ECHOSRC", &R, NULL);
InsertObject(PObj);
R = DEFAULT_DUMPLVL;
PObj = GenNumObject("DUMPLVL", &R, NULL);
InsertObject(PObj);
R = 0;
PObj = GenNumObject("FLAT4PLY", &R, NULL);
InsertObject(PObj);
R = MACHINE_UNIX;
#if defined(__MSDOS__) || defined(DJGCC)
R = MACHINE_MSDOS;
#endif
#if defined(sgi)
R = MACHINE_SGI;
#endif
#if defined(hpbsd) || defined(hpux)
R = MACHINE_HP;
#endif
#if defined(sun)
R = MACHINE_SUN;
#endif
#if defined(apollo)
R = MACHINE_APOLLO;
#endif
PObj = GenNumObject("MACHINE", &R, NULL);
InsertObject(PObj);
PObj = GenPolyObject("AXES", GenAxesObjectPolylines(), NULL);
SET_POLYLINE_OBJ(PObj); /* Mark it as polyline object. */
InsertObject(PObj);
}
/*****************************************************************************
* Routine to get the value to EchoSrc variable. *
*****************************************************************************/
static int GetDumpLevel(void)
{
int DumpLvl;
ObjectStruct
*PObj = GetObject("DUMPLVL");
if (PObj == NULL || !IS_NUM_OBJ(PObj)) {
WndwInputWindowPutStr("No numeric object name DumpLvl is defined");
DumpLvl = DEFAULT_DUMPLVL;
}
else
DumpLvl = (int) (PObj -> U.R);
return DumpLvl;
}
/*****************************************************************************
* Generate an axes system with length of 1 on each axis. *
*****************************************************************************/
static PolygonStruct *GenAxesObjectPolylines(void)
{
PolygonStruct *Pl, *PlHead;
VertexStruct *V;
Pl = PlHead = AllocPolygon(0, 0, NULL, NULL); /* X axis. */
Pl -> V = V = AllocVertex(0, 0, NULL, NULL);
V -> Pt[0] = 0.0; V -> Pt[1] = 0.0; V -> Pt[2] = 0.0;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] = 1.0; V -> Pt[1] = 0.0; V -> Pt[2] = 0.0;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] = 1.0; V -> Pt[1] = 0.1; V -> Pt[2] = 0.1;
Pl -> Pnext = AllocPolygon(0, 0, NULL, NULL); Pl = Pl -> Pnext;
Pl -> V = V = AllocVertex(0, 0, NULL, NULL);
V -> Pt[0] = 1.0; V -> Pt[1] = 0.1; V -> Pt[2] = 0.0;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] = 1.0; V -> Pt[1] = 0.0; V -> Pt[2] = 0.1;
Pl -> Pnext = AllocPolygon(0, 0, NULL, NULL); Pl = Pl -> Pnext;/* Y axis.*/
Pl -> V = V = AllocVertex(0, 0, NULL, NULL);
V -> Pt[0] = 0.0; V -> Pt[1] = 0.0; V -> Pt[2] = 0.0;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] = 0.0; V -> Pt[1] = 1.0; V -> Pt[2] = 0.0;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] = 0.0; V -> Pt[1] = 1.0; V -> Pt[2] = 0.06;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] = 0.04; V -> Pt[1] = 1.0; V -> Pt[2] = 0.1;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] = 0.0; V -> Pt[1] = 1.0; V -> Pt[2] = 0.06;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] =(-0.04);V -> Pt[1] = 1.0; V -> Pt[2] = 0.1;
Pl -> Pnext = AllocPolygon(0, 0, NULL, NULL); Pl = Pl -> Pnext;/* Z axis.*/
Pl -> V = V = AllocVertex(0, 0, NULL, NULL);
V -> Pt[0] = 0.0; V -> Pt[1] = 0.0; V -> Pt[2] = 0.0;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] = 0.0; V -> Pt[1] = 0.0; V -> Pt[2] = 1.0;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] = 0.1; V -> Pt[1] = 0.0; V -> Pt[2] = 1.0;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] = 0.0; V -> Pt[1] = 0.1; V -> Pt[2] = 1.0;
V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
V -> Pt[0] = 0.1; V -> Pt[1] = 0.1; V -> Pt[2] = 1.0;
return PlHead;
}
/*****************************************************************************
* Get the nth object in a list. Object reference is properly updated. *
*****************************************************************************/
ObjectStruct *GetNthList(ObjectStruct *ListObj, RealType *Rn)
{
int i,
n = REAL_PTR_TO_INT(Rn);
ObjectStruct *PObj;
if (!IS_OLST_OBJ(ListObj)) {
WndwInputWindowPutStr("None list object ignored.");
return NULL;
}
if (n < 1 || n >= MAX_OBJ_LIST) {
WndwInputWindowPutStr("Out of range of list.");
return NULL;
}
for (i = 0; i < n; i++) if (ListObj -> U.PObjList[i] == NULL) {
WndwInputWindowPutStr("Out of range of list.");
return NULL;
}
PObj = CopyObject(NULL, ListObj -> U.PObjList[n - 1], FALSE);
return PObj;
}
/*****************************************************************************
* Append two lists. Object reference is properly updated. *
*****************************************************************************/
ObjectStruct *AppendLists(ObjectStruct *ListObj1, ObjectStruct *ListObj2)
{
int i, j;
ObjectStruct *PObj;
if (!IS_OLST_OBJ(ListObj1) && !IS_OLST_OBJ(ListObj2)) {
WndwInputWindowPutStr("None list object ignored.");
return NULL;
}
PObj = AllocObject("", OBJ_LIST_OBJ, NULL);
for (i = 0; ListObj1 -> U.PObjList[i] != NULL && i < MAX_OBJ_LIST; i++) {
PObj -> U.PObjList[i] = ListObj1 -> U.PObjList[i];
PObj -> U.PObjList[i] -> Count++;
}
for (j = 0; ListObj2 -> U.PObjList[j] != NULL && i < MAX_OBJ_LIST; i++, j++) {
PObj -> U.PObjList[i] = ListObj2 -> U.PObjList[j];
PObj -> U.PObjList[i] -> Count++;
}
if (i < MAX_OBJ_LIST)
PObj -> U.PObjList[i] = NULL;
return PObj;
}
/*****************************************************************************
* Snoc (Cons to the end of the list, in place) the object to the list in *
* the second argument. *
*****************************************************************************/
void SnocList(ObjectStruct *PObj, ObjectStruct *ListObj)
{
int i;
if (!IS_OLST_OBJ(ListObj)) {
WndwInputWindowPutStr("None list object ignored.");
return;
}
for (i = 0; ListObj -> U.PObjList[i] != NULL && i < MAX_OBJ_LIST - 2; i++);
if (i < MAX_OBJ_LIST - 2) {
ListObj -> U.PObjList[i] = CopyObject(NULL, PObj, FALSE);
ListObj -> U.PObjList[i] -> Count = 1;
ListObj -> U.PObjList[i + 1] = NULL;
}
}
/*****************************************************************************
* Get object by its name - scans the object linear list. *
* Note the termination is also on 1000 objects (simple debugging aid in case *
* the Object list became circular), and a fatal error is produced. *
*****************************************************************************/
ObjectStruct *GetObject(char *ObjName)
{
int i = 0;
ObjectStruct
*PObj = GlblObjList;
while (PObj) {
if (strcmp(PObj -> Name, ObjName) == 0) {
return PObj;
}
PObj = PObj -> Pnext;
if (i++ >= 1000)
FatalError("GetObject: Global Object list too big (>1000)\n");
}
return NULL;
}
/*****************************************************************************
* Free Object - delete it from global active list and free all it memory *
*****************************************************************************/
void FreeObject(ObjectStruct *PObj)
{
/* Force free the object. Since the reference count should be actually */
/* two (second for the parsing tree reference) we decrement it here. */
if (PObj->Count == 2)
{
PObj->Count = 1;
DeleteObject(PObj, TRUE);
}
else
{
/* Reduce the reference count by two - one for the parsing tree */
/* this routine was called from and one for the fact this object */
/* reference count is to be deleted since this routine was called. */
DeleteObject(PObj, FALSE);
PObj->Count -= 2;
}
}
/*****************************************************************************
* Delete object by its pointer - scans the object linear list. *
* Note the deleted record is free only if Free = TRUE. *
*****************************************************************************/
void DeleteObject(ObjectStruct *PObj, int Free)
{
ObjectStruct
*PObjScan = GlblObjList;
if (GlblObjList == NULL) return;
if (PObj == GlblObjList) { /* If it is the first one - special case. */
GlblObjList = GlblObjList->Pnext;
if (Free) MyFree((char *) PObj, ALLOC_OBJECT);
return;
}
while (PObjScan->Pnext) {
if (PObj == PObjScan->Pnext) {
PObjScan->Pnext = PObjScan->Pnext->Pnext;/* Delete it from list. */
if (Free) MyFree((char *) PObj, ALLOC_OBJECT); /* And free it. */
return;
}
PObjScan = PObjScan->Pnext;
}
}
/*****************************************************************************
* Insert object by its pointer - as first in object linear list. *
* Note it is assumed the object is not in the object list allready. *
*****************************************************************************/
void InsertObject(ObjectStruct *PObj)
{
PObj -> Pnext = GlblObjList;
GlblObjList = PObj;
}
/*****************************************************************************
* Print one line. *
*****************************************************************************/
static void PrintOneLine(char *Line)
{
WndwInputWindowPutStr(Line);
}
/*****************************************************************************
* Print some usefull info on the given object. *
*****************************************************************************/
void PrintObject(ObjectStruct *PObj)
{
int Count = PObj -> Count;
CagdRType
DumpLvl = GetDumpLevel();
char Line[LINE_LEN_LONG],
*Name = (int) strlen(PObj -> Name) > 0 ? PObj -> Name : "NONE";
switch (PObj->ObjType) {
case UNDEF_OBJ:
sprintf(Line, "%-10s (%d) - Undefined type", Name, Count);
PrintOneLine(Line);
break;
case POLY_OBJ:
sprintf(Line, "%-10s (%d) - Poly type", Name, Count);
PrintOneLine(Line);
if (DumpLvl >= 3)
DataPrsrPutObject(NULL, PObj);
break;
case NUMERIC_OBJ:
sprintf(Line, "%-10s (%d) - Numeric type", Name, Count);
PrintOneLine(Line);
if (DumpLvl >= 1)
DataPrsrPutObject(NULL, PObj);
break;
case VECTOR_OBJ:
sprintf(Line, "%-10s (%d) - Vector type", Name, Count);
PrintOneLine(Line);
if (DumpLvl >= 1)
DataPrsrPutObject(NULL, PObj);
break;
case CTLPT_OBJ:
sprintf(Line, "%-10s (%d) - CtlPt type", Name, Count);
PrintOneLine(Line);
if (DumpLvl >= 1)
DataPrsrPutObject(NULL, PObj);
break;
case MATRIX_OBJ:
sprintf(Line, "%-10s (%d) - Matrix type", Name, Count);
PrintOneLine(Line);
if (DumpLvl >= 1)
DataPrsrPutObject(NULL, PObj);
break;
case STRING_OBJ:
sprintf(Line, "%-10s (%d) - String type", Name, Count);
PrintOneLine(Line);
if (DumpLvl >= 1)
DataPrsrPutObject(NULL, PObj);
break;
case OBJ_LIST_OBJ:
sprintf(Line, "%-10s (%d) - Object List type", Name, Count);
PrintOneLine(Line);
if (DumpLvl >= 4)
DataPrsrPutObject(NULL, PObj);
break;
case CURVE_OBJ:
sprintf(Line, "%-10s (%d) - Curve type", Name, Count);
PrintOneLine(Line);
if (DumpLvl >= 2) {
CagdSetCagdFprintf(PrintOneLine);
DataPrsrPutObject(NULL, PObj);
CagdSetCagdFprintf(NULL);
}
break;
case SURFACE_OBJ:
sprintf(Line, "%-10s (%d) - Surface type", Name, Count);
PrintOneLine(Line);
if (DumpLvl >= 2) {
CagdSetCagdFprintf(PrintOneLine);
DataPrsrPutObject(NULL, PObj);
CagdSetCagdFprintf(NULL);
}
break;
default:
sprintf(Line, "%-10s (%d) - Obj type error, type = %d",
Name, Count, PObj->ObjType);
PrintOneLine(Line);
break;
}
}
/*****************************************************************************
* Print some usefull info on all the given objects. *
*****************************************************************************/
void PrintObjectList(ObjectStruct *PObj)
{
PrintOneLine("");
while (PObj != NULL) {
PrintObject(PObj);
PObj = PObj -> Pnext;
}
}
/*****************************************************************************
* Generate one polygonal object: *
*****************************************************************************/
ObjectStruct *GenPolyObject(char *Name, PolygonStruct *Pl, ObjectStruct *Pnext)
{
ObjectStruct *PObj;
PObj = AllocObject(Name, POLY_OBJ, Pnext);
ResetObjectAttribs(PObj); /* Initialize attributes. */
RST_POLYLINE_OBJ(PObj); /* Default - not polyline object. */
PObj -> U.Pl.P = Pl; /* Link the union part of it... */
return PObj;
}
/*****************************************************************************
* Generate one curve object: *
*****************************************************************************/
ObjectStruct *GenCrvObject(char *Name, CagdCrvStruct *Crv, ObjectStruct *Pnext)
{
ObjectStruct *PObj;
PObj = AllocObject(Name, CURVE_OBJ, Pnext);
ResetObjectAttribs(PObj); /* Initialize attributes. */
PObj -> U.Crv.Crv = Crv; /* Link the union part of it... */
PObj -> U.Crv.PLPolys = NULL;
PObj -> U.Crv.CtlPoly = NULL;
return PObj;
}
/*****************************************************************************
* Generate one surface object: *
*****************************************************************************/
ObjectStruct *GenSrfObject(char *Name, CagdSrfStruct *Srf, ObjectStruct *Pnext)
{
ObjectStruct *PObj;
PObj = AllocObject(Name, SURFACE_OBJ, Pnext);
ResetObjectAttribs(PObj); /* Initialize attributes. */
PObj -> U.Srf.Srf = Srf; /* Link the union part of it... */
PObj -> U.Srf.PLPolys = NULL;
PObj -> U.Srf.CtlMesh = NULL;
PObj -> U.Srf.Polygons = NULL;
return PObj;
}
/*****************************************************************************
* Generate one control point object: *
* Only one of CagdCoords/Coords should be specified. *
*****************************************************************************/
ObjectStruct *GenCtlPtObject(char *Name, CagdPointType PtType,
CagdRType *CagdCoords, RealType *Coords, ObjectStruct *Pnext)
{
int i;
CagdBType
IsNotRational = !CAGD_IS_RATIONAL_PT(PtType);
ObjectStruct *PObj;
RealType *t;
PObj = AllocObject(Name, CTLPT_OBJ, Pnext);
PObj -> U.CtlPt.PtType = PtType;
t = PObj -> U.CtlPt.Coords;
if (CagdCoords != NULL)
for (i = IsNotRational;
i <= CAGD_NUM_OF_PT_COORD(PtType);
i++) t[i] = CagdCoords[i];
else
for (i = IsNotRational;
i <= CAGD_NUM_OF_PT_COORD(PtType);
i++) t[i] = Coords[i];
return PObj;
}
/*****************************************************************************
* Generate one numeric object: *
*****************************************************************************/
ObjectStruct *GenNumObject(char *Name, RealType *R, ObjectStruct *Pnext)
{
ObjectStruct *PObj;
PObj = AllocObject(Name, NUMERIC_OBJ, Pnext);
PObj -> U.R = *R; /* Link the union part of it... */
return PObj;
}
/*****************************************************************************
* Generate one vector object: *
*****************************************************************************/
ObjectStruct *GenVecObject(char *Name, RealType *Vec0, RealType *Vec1,
RealType *Vec2, ObjectStruct *Pnext)
{
ObjectStruct *PObj;
PObj = AllocObject(Name, VECTOR_OBJ, Pnext);
PObj -> U.Vec[0] = *Vec0; /* Link the union part of it... */
PObj -> U.Vec[1] = *Vec1;
PObj -> U.Vec[2] = *Vec2;
return PObj;
}
/*****************************************************************************
* Generate one matrix object: *
*****************************************************************************/
ObjectStruct *GenMatObject(char *Name, MatrixType Mat, ObjectStruct *Pnext)
{
int i, j;
ObjectStruct *PObj;
PObj = AllocObject(Name, MATRIX_OBJ, Pnext);
for (i = 0; i < 4; i++) /* Link the union part of it... */
for (j = 0; j < 4; j++) PObj -> U.Mat[i][j] = Mat[i][j];
return PObj;
}
/*****************************************************************************
* Routine to create a whole new copy of an object Src into Dest. *
* if Dest is NULL, new object is allocated, otherwise Dest itself is updated *
* If CopyAll then all the record is copied, otherwise, only its variant *
* element (i.e. no Name/Pnext coping) is been copied. *
*****************************************************************************/
ObjectStruct *CopyObject(ObjectStruct *Dest, ObjectStruct *Src, int CopyAll)
{
int index;
char Line[LINE_LEN];
if (Dest == NULL)
Dest = AllocObject("", Src->ObjType, NULL);
else {
Dest->ObjType = Src->ObjType;
}
if (Dest == Src) return Dest; /* Called with same object - ignore. */
if (CopyAll) {
strcpy(Dest->Name, Src->Name);
Dest->Pnext = Src->Pnext; /* Maybe assigning NULL is better!? */
}
switch (Src->ObjType) {
case POLY_OBJ:
Dest->U.Pl.P = CopyPolygonList(Src->U.Pl.P);
CopyGeomAttrib(Dest, Src);
Dest->U.Pl.IsPolyline = Src->U.Pl.IsPolyline;
break;
case NUMERIC_OBJ:
Dest->U.R = Src->U.R;
break;
case VECTOR_OBJ:
PT_COPY(Dest->U.Vec, Src->U.Vec);
break;
case CTLPT_OBJ:
GEN_COPY(&Dest->U.CtlPt, &Src->U.CtlPt,
CAGD_MAX_PT_SIZE * sizeof(RealType));
break;
case MATRIX_OBJ:
MAT_COPY(Dest->U.Mat, Src->U.Mat);
break;
case STRING_OBJ:
strcpy(Dest->U.Str, Src->U.Str);
break;
case OBJ_LIST_OBJ:
GEN_COPY(Dest->U.PObjList, Src->U.PObjList,
MAX_OBJ_LIST * sizeof(ObjectStruct *));
index = 0;
while (index < MAX_OBJ_LIST &&
Dest -> U.PObjList[index] != NULL)
Dest -> U.PObjList[index++] -> Count++; /* Inc. # of ref. */
break;
case CURVE_OBJ:
Dest->U.Crv.Crv = CagdCrvCopy(Src->U.Crv.Crv);
Dest->U.Crv.PLPolys = NULL;
Dest->U.Crv.CtlPoly = NULL;
CopyGeomAttrib(Dest, Src);
break;
case SURFACE_OBJ:
Dest->U.Srf.Srf = CagdSrfCopy(Src->U.Srf.Srf);
Dest->U.Srf.PLPolys = NULL;
Dest->U.Srf.CtlMesh = NULL;
Dest->U.Srf.Polygons = NULL;
CopyGeomAttrib(Dest, Src);
break;
default:
sprintf(Line,
"CopyObject Attemp to copy undefined object %s type %d\n",
Src->Name, Src->ObjType);
FatalError(Line);
}
return Dest;
}
/*****************************************************************************
* Routine to generate a new copy of an object polygon list. *
*****************************************************************************/
PolygonStruct *CopyPolygonList(PolygonStruct *Src)
{
PolygonStruct *Phead, *Ptail;
if (Src == NULL) return NULL;
/* Prepare the header of the new polygon list: */
Phead = Ptail = AllocPolygon(1, Src->Tags, CopyVList(Src->V), NULL);
PLANE_COPY(Ptail -> Plane, Src -> Plane);
Src = Src -> Pnext;
while (Src != NULL) {
Ptail -> Pnext = AllocPolygon(Src->Count, Src->Tags,
CopyVList(Src->V), NULL);
Ptail = Ptail -> Pnext;
PLANE_COPY(Ptail -> Plane, Src -> Plane);
Src = Src -> Pnext;
}
return Phead;
}
/*****************************************************************************
* Routine to generate a new copy of a polygon vertices list. *
*****************************************************************************/
VertexStruct *CopyVList(VertexStruct *Src)
{
VertexStruct *Phead, *Ptail,
*SrcFirst = Src;
if (Src == NULL) return NULL;
/* Prepare the header of the new vertex list: */
Phead = Ptail = AllocVertex(Src -> Count, Src -> Tags, NULL, NULL);
PT_COPY(Phead -> Pt, Src -> Pt);
PT_COPY(Phead -> Normal, Src -> Normal);
Src = Src -> Pnext;
while (Src != SrcFirst && Src != NULL) {
Ptail -> Pnext = AllocVertex(Src -> Count, Src -> Tags, NULL, NULL);
Ptail = Ptail -> Pnext;
PT_COPY(Ptail -> Pt, Src -> Pt);
PT_COPY(Ptail -> Normal, Src -> Normal);
Src = Src -> Pnext;
}
if (Src == SrcFirst) Ptail -> Pnext = Phead;/* Make vertex list circular.*/
return Phead;
}